//
// Created by Functioner on 2022/2/26.
//

#include "rdt_protocol.h"

#include "rdt_sender.h"
#include "rdt_receiver.h"

#include <cstring>

namespace RDTProtocol {
    // Global
    unsigned int CRC_32_Table[256];

    // Receiver
    std::vector<std::pair<bool, std::string>> receiverSaved;
    size_t receiverLeft;

    // Sender
    std::list<std::string> senderQueue;
    std::vector<std::pair<bool, std::string>> senderSaved;
    size_t senderLeft;
    size_t senderRight;

    // Timers
    std::map<int, int> activeTimers;
}

void RDTProtocol::startTimer(int id) {
    if (!Sender_isTimerSet()) {
        Sender_StartTimer(TimerInterval);
    }

    static const int default_delay = 1;

    auto it = activeTimers.find(id);
    if (it == activeTimers.end()) {
        activeTimers.insert(std::make_pair(id, default_delay));
    } else {
        it->second--;
    }
}

void RDTProtocol::killTimer(int id) {
    activeTimers.erase(id);
    if (activeTimers.empty() && Sender_isTimerSet()) {
        Sender_StopTimer();
    }
}

void RDTProtocol::onTimer() {
    auto timers = activeTimers;
    activeTimers.clear();

    // std::cout << "Time out" << std::endl;

    for (auto it = timers.begin(); it != timers.end(); ++it) {
        int timer_id = it->first;
        int &delay = it->second;
    // if (delay > 0) {
    //     activeTimers.insert(std::make_pair(timer_id, delay - 1));
    // } else {
        // std::cout << "Resend message " << timer_id << std::endl;
        SendByIndex(timer_id);
    // }
    }

    if (!Sender_isTimerSet() && !activeTimers.empty()) {
        Sender_StartTimer(TimerInterval);
    }
}

uint32_t RDTProtocol::CRC32_Verify(const std::string &data) {
    uint32_t crc = 0;
    crc = ~crc;
    for (char ch : data) {
        crc = (crc >> 8) ^ CRC_32_Table[(crc ^ ch) & 0xFF];
    }
    return ~crc;
}

std::string RDTProtocol::EncodeMessage(const std::string &data, int index) {
    std::string res;

    ASSERT(data.size() <= MaximumDataSize);

    // Header
    res.push_back(char(data.size()));
    res.push_back(char(index));

    // Data
    res.append(data);
    res.resize(MaximumDataSize + 2, '\0'); // Fill

    // Check code
    uint32_t crc = CRC32_Verify(res);
    res.append(std::string((char *) (&crc), sizeof(crc)));

    return res;
}

std::string RDTProtocol::DecodeMessage(const std::string &data, int &index) {
    std::string res;

    if (data.size() != RDT_PKTSIZE) {
        return res;
    }

    int size = uint8_t(data.at(0));
    int temp = uint8_t(data.at(1));

    std::string crc_str = data.substr(data.size() - 4);
    uint32_t crc_real = *(uint32_t *) (crc_str.data());
    uint32_t crc_theory = CRC32_Verify(data.substr(0, MaximumDataSize + 2));

    if (crc_real != crc_theory) {
        return res;
    }

    res = data.substr(2, size);
    index = temp;

    return res;
}

void RDTProtocol::SendByIndex(int index) {
    auto &pair = senderSaved[index];
    if (pair.first || pair.second.empty()) {
        return;
    }

    const std::string &message = pair.second;

    // Encapsulate
    std::string data = EncodeMessage(message, index);

    packet pkt{};
    memset(pkt.data, 0, RDT_PKTSIZE);
    memcpy(pkt.data, data.data(), RDT_PKTSIZE);

    Sender_ToLowerLayer(&pkt);
    startTimer(index);

    // std::cout << "Send message " << index << " " << message.size() << std::endl;
}

void RDTProtocol::ReceiveAcknowledge(const std::string &data) {
    int index;
    std::string message = DecodeMessage(data, index);
    // std::cout << "Ack " << index << std::endl;
    if (message != AckString) {
        // Message is damaged.
        return;
    }

    if (!WithinSenderWindow(index)) {
        // Shouldn't receive this ack. (should never happen)
        return;
    }
    if (senderSaved[index].first) {
        // This ack has been received.
        return;
    }

    killTimer(index);
    senderSaved[index].first = true;

    // Move window
    while (senderSaved[senderLeft].first) {
        senderSaved[senderLeft] = {false, ""};
        senderLeft = (senderLeft + 1) % SerialSize;
    }

    FillSenderWindow();
}

void RDTProtocol::FillSenderWindow() {
    while ((senderRight != senderLeft || (senderRight == senderLeft && senderSaved[senderLeft].second.empty())) &&
           !senderQueue.empty() && (senderRight + SerialSize - senderLeft) % SerialSize < WindowSize) {
        // Retrieve first message of queue and append to sender window
        senderSaved[senderRight] = {false, senderQueue.front()};
        senderQueue.pop_front();
        // Send
        SendByIndex(senderRight);
        // Sender end pointer move next
        senderRight = (senderRight + 1) % SerialSize;
    }
}

bool RDTProtocol::WithinSenderWindow(int index) {
    if (senderRight >= senderLeft) {
        return index >= senderLeft && index < senderRight;
    } else {
        return index >= senderLeft || index < senderRight;
    }
}

void RDTProtocol::ReceiveMessage(const std::string &data) {
    int index;
    std::string message = DecodeMessage(data, index);
    if (message.empty()) {
        // Message is damaged.
        return;
    }

    if (!WithinReceiverWindow(index)) {
        // Previous message with same index's ack is lost.
        SendAcknowledge(index);
        return;
    }
    if (receiverSaved[index].first) {
        // This message has been received but ack is lost.
        SendAcknowledge(index);
        return;
    }

    // std::cout << "Receive message " << index << " " << message.size() << std::endl;

    receiverSaved[index] = {true, message};

    SendAcknowledge(index);
}

void RDTProtocol::SendAcknowledge(int index) {
    // Encapsulate
    std::string data = EncodeMessage(AckString, index);

    packet pkt{};
    memset(pkt.data, 0, RDT_PKTSIZE);
    memcpy(pkt.data, data.data(), RDT_PKTSIZE);

    Receiver_ToLowerLayer(&pkt);

    // Move window
    while (receiverSaved[receiverLeft].first) {
        ReportMessage(receiverSaved[receiverLeft].second);
        receiverSaved[receiverLeft] = {false, ""};
        receiverLeft = (receiverLeft + 1) % SerialSize;
    }
}

void RDTProtocol::ReportMessage(const std::string &data) {
    message msg{};
    msg.size = data.size();
    msg.data = new char[msg.size];
    memcpy(msg.data, data.data(), msg.size);

    Receiver_ToUpperLayer(&msg);

    delete[]msg.data;
}

bool RDTProtocol::WithinReceiverWindow(int index) {
    auto receiverRight = (receiverLeft + WindowSize) % SerialSize;
    if (receiverRight >= receiverLeft) {
        return index >= receiverLeft && index < receiverRight;
    } else {
        return index >= receiverLeft || index < receiverRight;
    }
}

void RDTProtocol::GenerateCRC32_Table() {
    int crc;
    for (int i = 0; i < 256; ++i) {
        crc = i;
        for (int j = 8; j > 0; --j) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ CRC32_Key;
            } else {
                crc = (crc << 1);
            }
        }
        CRC_32_Table[i] = crc;
    }
}

void RDTProtocol::SenderInit() {
    GenerateCRC32_Table();
    senderSaved.resize(SerialSize, {false, ""});
    senderLeft = 0;
    senderRight = 0;
}

void RDTProtocol::ReceiverInit() {
    receiverSaved.resize(SerialSize, {false, ""});
    receiverLeft = 0;
}